﻿//#define TRACE_FRAMES

using System.Diagnostics;
using TI_BLE_HCI_ClientLib.HCIBaseEntities;
using TI_BLE_HCI_ClientLib.HCIParsers;
using Utilities;
using Transport.Interfaces;

namespace TI_BLE_HCI_ClientLib.FrameManagement
{
    public class FullDuplexChannelFrameManager : IFrameManager
    {
        private IFullDuplexChannel _channel;

        public FullDuplexChannelFrameManager(IFullDuplexChannel channel)
        {
            _channel = channel;

            _channel.DataReceived += _channel_DataReceived;
        }

        private void _channel_DataReceived(byte[] buffer)
        {

#if TRACE_FRAMES
            DiagnosticLog.TraceInformation("FullDuplexChannelFrameManager <RX> : " + buffer.ToHexString()); 
#endif

            for (var i = 0; i < buffer.Length; i++)
            {
                try
                {
                    processByte(buffer[i]);
                }
                catch
                {
                    resetStateMachine();
                }
            }
        }

        public void Start()
        {
            _channel.Start();
        }

        public void Stop()
        {
            _channel.Stop();
        }

        public void SendFrame(BaseEntities.BaseProtocolEntity protocolEntity)
        {
            var buf = protocolEntity.ToByteArray();

#if TRACE_FRAMES
            DiagnosticLog.TraceInformation("FullDuplexChannelFrameManager Sending : " + buf.ToHexString());
#endif

            _channel.SendData(buf);
        }

        public event FrameReceivedEventHandler FrameReceived;

        ///////////////////////////////////////////////////

        private enum ProcessingState
        {
            STATE_IDLE,
            STATE_HEADER_RECEIVED,
            STATE_COMMAND_OPCODE_L_RECEIVED,
            STATE_COMMAND_OPCODE_H_RECEIVED,
            STATE_ASYNC_DATA_HANDLE_L_RECEVIED,
            STATE_ASYNC_DATA_HANDLE_H_RECEVIED,
            STATE_EVENT_CODE_RECEIVED,
            STATE_PARAMETERS_TOTAL_LEN_RECEIVED
        }

        private enum PacketHeader
        {
            None = 0,
            Command = 1,
            AsynchronousData = 2,
            SynchronousData = 3,
            Event = 4
        }

        private void resetStateMachine()
        {
            _currentState = ProcessingState.STATE_IDLE;
            _packetHeader = PacketHeader.None;
            _buffer = null;
            _bufIdx = 0;
            _eventCode = 0;
            _parsLen = 0;
        }

        private ProcessingState _currentState = ProcessingState.STATE_IDLE;

        private byte _bufIdx;
        private byte[] _buffer;
        private byte _parsLen;
        private PacketHeader _packetHeader = PacketHeader.None;

        private byte _eventCode;

        private void processByte(byte b)
        {
            switch (_currentState)
            {
                case ProcessingState.STATE_IDLE:
                    switch (b)
                    {
                        case (byte)PacketHeader.Event:
                            _packetHeader = (PacketHeader)b;
                            _currentState = ProcessingState.STATE_HEADER_RECEIVED;
                            break;
                    }
                    break;

                case ProcessingState.STATE_HEADER_RECEIVED:
                    switch (_packetHeader)
                    {
                        case PacketHeader.Event:
                            _eventCode = b;
                            _currentState = ProcessingState.STATE_EVENT_CODE_RECEIVED;
                            break;
                    }
                    break;

                case ProcessingState.STATE_EVENT_CODE_RECEIVED:
                    _parsLen = b;
                    _bufIdx = 2;
                    _buffer = new byte[2 + _parsLen];
                    _buffer[0] = _eventCode;
                    _buffer[1] = _parsLen;
                    _currentState = ProcessingState.STATE_PARAMETERS_TOTAL_LEN_RECEIVED;
                    break;

                case ProcessingState.STATE_PARAMETERS_TOTAL_LEN_RECEIVED:
                    _buffer[_bufIdx] = b;
                    _bufIdx++;

                    if (_bufIdx == 2 + _parsLen)
                    {
                        frameComplete();
                        resetStateMachine();
                    }

                    break;
            }
        }

        private void frameComplete()
        {
            switch (_packetHeader)
            {
                case PacketHeader.Event:
                    var hciEvent = HCIEventParser.Parse(_buffer);
                    raiseEvent(hciEvent);
                    break;
            }
        }

        private void raiseEvent(HCIEvent hciEvent)
        {
            if (FrameReceived != null)
            {
                FrameReceived(hciEvent);
            }
        }
    }
}